#include "gtest/gtest.h"
//#include "DataStructure/Common/include/Constant.h"
#include "SparseBlas.h"

using namespace SG::Algebra;  // Open the namespace
//using SG::DataStructure::Common::TOLERANCE;

class SparseBlasTest : public ::testing::Test
{
  public:
    static void SetUpTestSuite ()
    {
    }
};

/*
 * A = [A11,A12;A21,A22]
 *
 * A11 =
 *  [
 *       19.00,  00.00,  21.00;
 *       12.00,  21.00,  00.00;
 *       00.00,  12.00,  16.00;
 *  ]
 *
 * A21 =
 *  [
 *       21.00,  00.00;
 *       00.00,  00.00;
 *  ]
 *
 * A21 =
 *  [
 *       00.00,  00.00,  00.00;
 *       12.00,  12.00,  00.00
 *  ]
 *
 * A22 =
 *  [
 *       05.00,  21.00;
 *       00.00,  18.00
 *  ]
 */
TEST_F (SparseBlasTest, BlockedValuesTest)
{
    typedef Eigen::Triplet<double> Triplet;

    int              M = 5;
    int              N = 5;

    ComplexSpMat_t A (M, N);

    // A11 = A(0:3, 0:3)
    SpMat_t A11 (3, 3);
    {
        std::vector<Triplet> triplets;
        triplets.push_back (Triplet (0, 0, 19.0));
        triplets.push_back (Triplet (0, 2, 21.0));
        triplets.push_back (Triplet (1, 0, 12.0));
        triplets.push_back (Triplet (1, 1, 21.0));
        triplets.push_back (Triplet (2, 1, 12.0));
        triplets.push_back (Triplet (2, 2, 16.0));

        A11.setFromTriplets (triplets.begin (), triplets.end ());

        SparseBlas::setValues (A, 0, 0, A11);
    }

    // A12 = A(0:3, 3:5)
    SpMat_t A12 (3, 2);
    {
        std::vector<Triplet> triplets;
        triplets.push_back (Triplet (0, 0, 21.0));

        A12.setFromTriplets (triplets.begin (), triplets.end ());

        SparseBlas::setValues (A, 0, 3, A12);
    }

    // A21 = A(3:5, 0:3)
    SpMat_t A21 (2, 3);
    {
        std::vector<Triplet> triplets;
        triplets.push_back (Triplet (1, 0, 12.0));
        triplets.push_back (Triplet (1, 1, 12.0));

        A21.setFromTriplets (triplets.begin (), triplets.end ());

        SparseBlas::setValues (A, 3, 0, A21);
    }

    // A22 = A(3:5, 3:5)
    SpMat_t A22 (2, 2);
    {
        std::vector<Triplet> triplets;
        triplets.push_back (Triplet (0, 0, 5.0));
        triplets.push_back (Triplet (0, 1, 21.0));
        triplets.push_back (Triplet (1, 1, 18.0));

        A22.setFromTriplets (triplets.begin (), triplets.end ());

        SparseBlas::setValues (A, 3, 3, A22);
    }

    ASSERT_NEAR (A.coeff(0,0).real(), 19.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(0, 2).real (), 21.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(0, 3).real (), 21.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(1, 0).real (), 12.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(1, 1).real (), 21.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(2, 1).real (), 12.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(2, 2).real (), 16.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(3, 3).real (), 5.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(3, 4).real (), 21.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(4, 0).real (), 12.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(4, 1).real (), 12.0, TOLERANCE);
    ASSERT_NEAR (A.coeff(4, 4).real (), 18.0, TOLERANCE);

#ifdef SG_VERBOSE
    std::cout << "A = " << std::endl;
    //std::cout << A << std::endl;
#endif
}

/*
 * Consider y = A * x, where
 *
 * A =
 *   [
 *       19.00,  00.00,  21.00,  21.00, 00.00;
 *       12.00,  21.00,  00.00,  00.00, 00.00;
 *       00.00,  12.00,  16.00,  00.00, 00.00;
 *       00.00,  00.00,  00.00,  05.00, 21.00;
 *       12.00,  12.00,  00.00,  00.00, 18.00
 *   ]
 *
 * x = [0.0; 1.0; 2.0; 3.0; 4.0]
 *
 * OctaveOnline give the solution as followings,
 * y = [105; 21; 44; 99; 84]
 *
 * see https://octave-online.net/
 */
TEST_F (SparseBlasTest, multiplyTest)
{
    typedef Eigen::Triplet<double> Triplet;
    std::vector<Triplet>           triplets;

    triplets.push_back (Triplet (0, 0, 19.0));
    triplets.push_back (Triplet (0, 2, 21.0));
    triplets.push_back (Triplet (0, 3, 21.0));

    triplets.push_back (Triplet (1, 0, 12.0));
    triplets.push_back (Triplet (1, 1, 21.0));

    triplets.push_back (Triplet (2, 1, 12.0));
    triplets.push_back (Triplet (2, 2, 16.0));

    triplets.push_back (Triplet (3, 3, 5.0));
    triplets.push_back (Triplet (3, 4, 21.0));

    triplets.push_back (Triplet (4, 0, 12.0));
    triplets.push_back (Triplet (4, 1, 12.0));
    triplets.push_back (Triplet (4, 4, 18.0));

    int m = 5;  // row count
    int n = 5;  // column count

    SpMat_t A (m, n);
    A.setFromTriplets (triplets.begin (), triplets.end ());

    Vec_t X(n), Y(m);

    for (int i = 0; i < n; ++ i)
    {
        X[i] = i;
        Y[i] = 0.0;
    }

    Vec_t Y_exact (m);
    Y_exact[0] = 105;
    Y_exact[1] = 21;
    Y_exact[2] = 44;
    Y_exact[3] = 99;
    Y_exact[4] = 84;

    auto info = SparseBlas::multiply (A, X, Y);

    // Check the results...
    ASSERT_TRUE (Info_t::Success == info);

    // for (int i = 0; i < m; ++i)
    // {
    //     std::cout << Y_exact[i] << ", " << Y[i] << std::endl;
    // }

    for (int i = 0; i < m; ++ i)
    {
        ASSERT_NEAR (Y_exact[i], Y[i], TOLERANCE);
    }
}